使用 sockopt 实现内核与用户层通信

基于socket的sockopt是Linux中一种简单易用的内核态与用户态的通信方式。

应用层-系统调用

Linux中有两个系统调用,setsockopt和getsockopt,用于​设置、获取socket选项。它们的函数原型为:

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

其中s即socket_fd,套接字的文件描述符。

level指定在哪一层控制socket选项,常用的有以下三种:

  1. SOL_SOCKET(= 1),通用套接字选项;
  2. IPPROTO_IP(= 0),IP选项;
  3. IPPROTO_TCG(= 6),TCP选项;

optname为选项代号。

对于getsockopt,optval,optlen为返回的选项值的指针及长度。比如,一个字符串或结构体指针和它的长度。对于setsockopt,类似,optval,optlen为要设置的数据指针和数据长度。

如果函数执行成功,返回值为0,否则返回-1。错误代号保存在errno中。

举个例子:

int tmp = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));

以上代码设置套接字地址和端口可重用。

内核层-netfilter

Linux的netfilter提供了一组接口用于自定义socket选项。

数据结构:

struct nf_sockopt_ops
{
        struct list_head list;

        int pf; 

        /* Non-inclusive ranges: use 0/0/NULL to never get called. */
        /* setsockopt选项编号取值范围 */
        int set_optmin;
        int set_optmax;
        /* 自定义setsockopt函数, sk为套接字内核内部数据结构 */
        int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);
        int (*compat_set)(struct sock *sk, int optval,
                        void __user *user, unsigned int len);

        /* getsockopt选项编号取值范围 */
        int get_optmin;
        int get_optmax;
        /* 自定义setsockopt函数 */
        int (*get)(struct sock *sk, int optval, void __user *user, int *len);
        int (*compat_get)(struct sock *sk, int optval,
                        void __user *user, int *len);

        /* Number of users inside set() or get(). */
        unsigned int use;
        struct task_struct *cleanup_task;
};

我们只要新建一个nf_sockopt_ops结构,设置好选项取值范围,自定义set和get函数,然后注册到内核中。这样就可以在用户层调用setsockopt或getsockopt,和内核通信了。
注册卸载nf_sockopt_ops的接口为:

int nf_register_sockopt(struct nf_sockopt_ops *reg);
void nf_unregister_sockopt(struct nf_sockopt_ops *reg);

示例

示例一:互相发送字符串

内核模块代码

sockopt_kern.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/netfilter_ipv4.h>
#include <linux/init.h>
#include <asm/uaccess.h>

#define SOCKET_OPS_BASE 128
#define SOCKET_OPS_SET (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1)

#define KMSG "a message from kernel"
#define KMSG_LEN sizeof("a message from kernel")

MODULE_LICENSE("GPL");

static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len)
{

int ret = 0;
printk(KERN_INFO "sockopt: recv_msg()\n");

if (cmd == SOCKET_OPS_SET)
{
char umsg[64];
int len = sizeof(char)*64;
memset(umsg, 0, len);
ret = copy_from_user(umsg, user, len);
printk("recv_msg: umsg = %s. ret = %d\n", umsg, ret);
}
return 0;
}

static int send_msg(struct sock *sk, int cmd, void *user, int *len)
{

int ret = 0;
printk(KERN_INFO "sockopt: send_msg()\n");
if (cmd == SOCKET_OPS_GET)
{
ret = copy_to_user(user, KMSG, KMSG_LEN);
printk("send_msg: umsg = %s. ret = %d. success\n", KMSG, ret);
}
return 0;
}

static struct nf_sockopt_ops test_sockops =
{
.pf = PF_INET,
.set_optmin = SOCKET_OPS_SET,
.set_optmax = SOCKET_OPS_MAX,
.set = recv_msg,
.get_optmin = SOCKET_OPS_GET,
.get_optmax = SOCKET_OPS_MAX,
.get = send_msg,
};

static int __init init_sockopt(void)
{

printk(KERN_INFO "sockopt: init_sockopt()\n");
return nf_register_sockopt(&test_sockops);
}

static void __exit fini_sockopt(void)
{

printk(KERN_INFO "sockopt: fini_sockopt()\n");
nf_unregister_sockopt(&test_sockops);
}

module_init(init_sockopt);
module_exit(fini_sockopt);

应用层代码

sockopt_usr.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
#include <errno.h>

#define SOCKET_OPS_BASE 128
#define SOCKET_OPS_SET (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1)

#define UMSG "a message from userspace"
#define UMSG_LEN sizeof("a message from userspace")

char kmsg[64];

int main()
{

int sockfd;
int len;
int ret;

sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sockfd < 0)
{
printf("can not create a socket\n");
return -1;
}

/*call function recv_msg()*/
ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN);
printf("setsockopt: ret = %d. msg = %s\n", ret, UMSG);
len = sizeof(char)*64;

/*call function send_msg()*/
ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len);
printf("getsockopt: ret = %d. msg = %s\n", ret, kmsg);
if (ret != 0)
{
printf("getsockopt error: errno = %d, errstr = %s\n", errno, strerror(errno));
}

close(sockfd);
return 0;
}

示例二:用户层获取tcp连接对端窗口

内核模块代码

rcvwnd.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/tcp.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <net/sock.h>
#include <linux/netfilter_ipv4.h>

#define SOCKET_OPS_RCVWND 128

int get_rcv_wnd(struct sock *sk, int cmd, void __user *user, int *len)
{
if (cmd == SOCKET_OPS_RCVWND) {
struct tcp_sock *tsk;

if (sk == NULL)
return -1;

if ((tsk = tcp_sk(sk)) == NULL)
return -1;

copy_to_user(user, &tsk->rcv_wnd, sizeof(tsk->rcv_wnd));
}
return 0;
}

static struct nf_sockopt_ops rcvwnd_sockops = {
.pf = PF_INET,
.set_optmin = 0,
.set_optmax = 0,
.set = NULL,
.get_optmin = SOCKET_OPS_RCVWND,
.get_optmax = SOCKET_OPS_RCVWND + 1,
.get = get_rcv_wnd,
};

int minit(void)
{
return nf_register_sockopt(&rcvwnd_sockops);
}

void mexit(void)
{
nf_unregister_sockopt(&rcvwnd_sockops);
}

module_init(minit);
module_exit(mexit);
MODULE_LICENSE("GPL");

应用层用法

建立连接后,对tcp连接的文件描述符sock_fd调用getsockopt。

u32 rcvwnd = 0;
int len = 0;
getsockopt(sock_fd, IPPROTO, SOCKET_OPS_RCVWND, &rcvwnd, &len);